using System;
using System.Diagnostics;
using System.Text;
using NUnit.Framework;

namespace PaulRz.Util
{
    /// <summary>
    /// Default ASCII is 7bit encoding (i.e. it ignores 8th bit in input bytes)
    /// </summary>
    public sealed class ASCII8bitEncoding : Encoding
    {
        // both classes are state-less and that is why they are thread-safe
        private static Encoder encoder = new ASCII8bitEncoder();
        private static Decoder decoder = new ASCII8bitDecoder();

        public static ASCII8bitEncoding ASCII8bit = new ASCII8bitEncoding();

        ///<summary>
        ///When overridden in a derived class, calculates the number of bytes produced by encoding a set of characters from the specified character array.
        ///</summary>
        ///
        ///<returns>
        ///The number of bytes produced by encoding the specified characters.
        ///</returns>
        ///
        ///<param name="chars">The character array containing the set of characters to encode. </param>
        ///<param name="count">The number of characters to encode. </param>
        ///<param name="index">The index of the first character to encode. </param>
        ///<exception cref="T:System.ArgumentOutOfRangeException">index or count is less than zero.-or- index and count do not denote a valid range in chars. </exception>
        ///<exception cref="T:System.ArgumentNullException">chars is null. </exception><filterpriority>1</filterpriority>
        public override int GetByteCount(char[] chars, int index, int count)
        {
            return encoder.GetByteCount(chars, index, count, true);
        }

        ///<summary>
        ///When overridden in a derived class, encodes a set of characters from the specified character array into the specified byte array.
        ///</summary>
        ///
        ///<returns>
        ///The actual number of bytes written into bytes.
        ///</returns>
        ///
        ///<param name="chars">The character array containing the set of characters to encode. </param>
        ///<param name="charIndex">The index of the first character to encode. </param>
        ///<param name="charCount">The number of characters to encode. </param>
        ///<param name="byteIndex">The index at which to start writing the resulting sequence of bytes. </param>
        ///<param name="bytes">The byte array to contain the resulting sequence of bytes. </param>
        ///<exception cref="T:System.ArgumentNullException">chars is null.-or- bytes is null. </exception>
        ///<exception cref="T:System.ArgumentException">bytes does not have enough capacity from byteIndex to the end of the array to accommodate the resulting bytes. </exception>
        ///<exception cref="T:System.ArgumentOutOfRangeException">charIndex or charCount or byteIndex is less than zero.-or- charIndex and charCount do not denote a valid range in chars.-or- byteIndex is not a valid index in bytes. </exception><filterpriority>1</filterpriority>
        public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
        {
            return encoder.GetBytes(chars, charIndex, charCount, bytes, byteIndex, true);
        }

        ///<summary>
        ///When overridden in a derived class, calculates the number of characters produced by decoding a sequence of bytes from the specified byte array.
        ///</summary>
        ///
        ///<returns>
        ///The number of characters produced by decoding the specified sequence of bytes.
        ///</returns>
        ///
        ///<param name="count">The number of bytes to decode. </param>
        ///<param name="index">The index of the first byte to decode. </param>
        ///<param name="bytes">The byte array containing the sequence of bytes to decode. </param>
        ///<exception cref="T:System.ArgumentNullException">bytes is null. </exception>
        ///<exception cref="T:System.ArgumentOutOfRangeException">index or count is less than zero.-or- index and count do not denote a valid range in bytes. </exception><filterpriority>1</filterpriority>
        public override int GetCharCount(byte[] bytes, int index, int count)
        {
            return decoder.GetCharCount(bytes, index, count, true);
        }

        ///<summary>
        ///When overridden in a derived class, decodes a sequence of bytes from the specified byte array into the specified character array.
        ///</summary>
        ///
        ///<returns>
        ///The actual number of characters written into chars.
        ///</returns>
        ///
        ///<param name="chars">The character array to contain the resulting set of characters. </param>
        ///<param name="byteCount">The number of bytes to decode. </param>
        ///<param name="byteIndex">The index of the first byte to decode. </param>
        ///<param name="charIndex">The index at which to start writing the resulting set of characters. </param>
        ///<param name="bytes">The byte array containing the sequence of bytes to decode. </param>
        ///<exception cref="T:System.ArgumentOutOfRangeException">byteIndex or byteCount or charIndex is less than zero.-or- byteindex and byteCount do not denote a valid range in bytes.-or- charIndex is not a valid index in chars. </exception>
        ///<exception cref="T:System.ArgumentNullException">bytes is null.-or- chars is null. </exception>
        ///<exception cref="T:System.ArgumentException">chars does not have enough capacity from charIndex to the end of the array to accommodate the resulting characters. </exception><filterpriority>1</filterpriority>
        public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
        {
            return decoder.GetChars(bytes, byteIndex, byteCount, chars, charIndex);
        }

        public byte[] GetCharsAsByte(byte[] str)
        {
            char[] chrs = GetChars(str);
            return Array.ConvertAll(chrs, converter);
        }

        private static Converter<char, byte> converter = new Converter<char, byte>(C2BConverter);

        private static byte C2BConverter(char input)
        {
            return (byte)input;
        }

        ///<summary>
        ///When overridden in a derived class, calculates the maximum number of bytes produced by encoding the specified number of characters.
        ///</summary>
        ///
        ///<returns>
        ///The maximum number of bytes produced by encoding the specified number of characters.
        ///</returns>
        ///
        ///<param name="charCount">The number of characters to encode. </param>
        ///<exception cref="T:System.ArgumentOutOfRangeException">charCount is less than zero. </exception><filterpriority>1</filterpriority>
        public override int GetMaxByteCount(int charCount)
        {
            return ASCII8bitEncoder.GetMaxBytes(charCount);
        }

        ///<summary>
        ///When overridden in a derived class, calculates the maximum number of characters produced by decoding the specified number of bytes.
        ///</summary>
        ///
        ///<returns>
        ///The maximum number of characters produced by decoding the specified number of bytes.
        ///</returns>
        ///
        ///<param name="byteCount">The number of bytes to decode. </param>
        ///<exception cref="T:System.ArgumentOutOfRangeException">byteCount is less than zero. </exception><filterpriority>1</filterpriority>
        public override int GetMaxCharCount(int byteCount)
        {
            return ASCII8bitDecoder.GetMaxChars(byteCount);
        }

        #region Tests

        [Test]
        public static void testEncDec()
        {
            string test = "teststring";
            Encoding ascii8bit = new ASCII8bitEncoding();
            byte[] bytes = ascii8bit.GetBytes(test);
            string ntest = ascii8bit.GetString(bytes);
            Console.WriteLine("Source : {0}", test);
            Console.WriteLine("Dest   : {0}", ntest);
            Console.WriteLine("Equivalence : " + test.CompareTo(ntest));
        }

        private static bool GetIsEqual(byte[] bytes, byte[] testStr)
        {
            bool isEqual = true;
            if (testStr.Length == bytes.Length)
            {
                for (int i = 0; i < bytes.Length; ++i)
                {
                    if (bytes[i] != testStr[i])
                    {
                        isEqual = false;
                        break;
                    }
                }
            }
            else
            {
                isEqual = false;
                Console.WriteLine("Different sizes!");
            }
            return isEqual;
        }

        [Test]
        public static void FullTest()
        {
            const int SIZE = 300;
            byte[] testStr = new byte[SIZE];
            for(int i=0; i<SIZE; ++i)
            {
                testStr[i] = (byte)i;
            }

            string internalStr = ASCII8bit.GetString(testStr);
            byte[] bytes = ASCII8bit.GetBytes(internalStr);
            Console.WriteLine("IsEqual:{0}", GetIsEqual(testStr, bytes));
        }

        [Test]
        public static void perfTest()
        {
            const int SIZE = 300;
            byte[] testStr = new byte[SIZE];
            for (int i = 0; i < SIZE; ++i)
            {
                testStr[i] = (byte)i;
            }

            const int ITER = 100000;

            Stopwatch sw = new Stopwatch();
            sw.Start();
            
            for(int i=0; i<ITER; ++i)
            {
                string intStr = ASCII8bit.GetString(testStr);
                byte[] bytes = ASCII8bit.GetBytes(intStr);
            }
            sw.Stop();
            Console.WriteLine("{0} ms", sw.ElapsedMilliseconds);
        }

        #endregion
    }

    internal sealed class ASCII8bitEncoder : Encoder
    {
        ///<summary>
        ///When overridden in a derived class, calculates the number of bytes produced by encoding a set of characters from the specified character array. A parameter indicates whether to clear the internal state of the encoder after the calculation.
        ///</summary>
        ///
        ///<returns>
        ///The number of bytes produced by encoding the specified characters and any characters in the internal buffer.
        ///</returns>
        ///
        ///<param name="chars">The character array containing the set of characters to encode. </param>
        ///<param name="count">The number of characters to encode. </param>
        ///<param name="flush">true to simulate clearing the internal state of the encoder after the calculation; otherwise, false. </param>
        ///<param name="index">The index of the first character to encode. </param>
        ///<exception cref="T:System.ArgumentOutOfRangeException">index or count is less than zero.-or- index and count do not denote a valid range in chars. </exception>
        ///<exception cref="T:System.ArgumentNullException">chars is null. </exception><filterpriority>2</filterpriority>
        public override int GetByteCount(char[] chars, int index, int count, bool flush)
        {
            if (chars == null)
                throw new ArgumentNullException("chars");

            if ((index < 0) || (chars.Length - 1 < index))
                throw new ArgumentOutOfRangeException("index", index, "Index is out of range");

            if ((count < 0) || (index + count > chars.Length))
                throw new ArgumentOutOfRangeException("count", count, "Count is out of range");

            // in the case it's the same
            return GetMaxBytes(count);
        }

        ///<summary>
        ///When overridden in a derived class, encodes a set of characters from the specified character array and any characters in the internal buffer into the specified byte array. A parameter indicates whether to clear the internal state of the encoder after the conversion.
        ///</summary>
        ///
        ///<returns>
        ///The actual number of bytes written into bytes.
        ///</returns>
        ///
        ///<param name="chars">The character array containing the set of characters to encode. </param>
        ///<param name="charIndex">The index of the first character to encode. </param>
        ///<param name="charCount">The number of characters to encode. </param>
        ///<param name="byteIndex">The index at which to start writing the resulting sequence of bytes. </param>
        ///<param name="flush">true to clear the internal state of the encoder after the conversion; otherwise, false. </param>
        ///<param name="bytes">The byte array to contain the resulting sequence of bytes. </param>
        ///<exception cref="T:System.ArgumentNullException">chars is null.-or- bytes is null. </exception>
        ///<exception cref="T:System.ArgumentException">bytes does not have enough capacity from byteIndex to the end of the array to accommodate the resulting bytes. </exception>
        ///<exception cref="T:System.ArgumentOutOfRangeException">charIndex or charCount or byteIndex is less than zero.-or- charIndex and charCount do not denote a valid range in chars.-or- byteIndex is not a valid index in bytes. </exception><filterpriority>2</filterpriority>
        public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex, bool flush)
        {
            if (chars == null)
                throw new ArgumentNullException("chars");
            if (bytes == null)
                throw new ArgumentNullException("bytes");

            if (charIndex < 0 || (charIndex >= chars.Length))
                throw new ArgumentOutOfRangeException("charIndex");
            if ((charIndex + charCount > chars.Length))
                throw new ArgumentOutOfRangeException("charIndex/charCount");
            if (byteIndex < 0 || (byteIndex >= bytes.Length))
                throw new ArgumentOutOfRangeException("byteIndex");

            int byteCount = GetByteCount(chars, charIndex, charCount, true);

            if (bytes.Length < byteIndex + byteCount)
                throw new ArgumentOutOfRangeException("bytes", "Array bytes is not enough");

            for (int i = 0; i < charCount; ++i)
            {
                bytes[byteIndex + i] = (byte) (chars[charIndex + i] & 0x00FF);
            }

            return byteCount;
        }

        internal static int GetMaxBytes(int count)
        {
            return count;
        }
    }

    internal sealed class ASCII8bitDecoder : Decoder
    {
        ///<summary>
        ///When overridden in a derived class, calculates the number of characters produced by decoding a sequence of bytes from the specified byte array.
        ///</summary>
        ///
        ///<returns>
        ///The number of characters produced by decoding the specified sequence of bytes and any bytes in the internal buffer.
        ///</returns>
        ///
        ///<param name="count">The number of bytes to decode. </param>
        ///<param name="index">The index of the first byte to decode. </param>
        ///<param name="bytes">The byte array containing the sequence of bytes to decode. </param>
        ///<exception cref="T:System.ArgumentNullException">bytes is null. </exception>
        ///<exception cref="T:System.ArgumentOutOfRangeException">index or count is less than zero.-or- index and count do not denote a valid range in bytes. </exception><filterpriority>2</filterpriority>
        public override int GetCharCount(byte[] bytes, int index, int count)
        {
            if (bytes == null)
                throw new ArgumentNullException("bytes");

            if ((index < 0) || (bytes.Length - 1 < index))
                throw new ArgumentOutOfRangeException("index", index, "Index is out of range");

            if ((count < 0) || (index + count > bytes.Length))
                throw new ArgumentOutOfRangeException("count", count, "Count is out of range");

            return GetMaxChars(count);
        }

        ///<summary>
        ///When overridden in a derived class, decodes a sequence of bytes from the specified byte array and any bytes in the internal buffer into the specified character array.
        ///</summary>
        ///
        ///<returns>
        ///The actual number of characters written into chars.
        ///</returns>
        ///
        ///<param name="chars">The character array to contain the resulting set of characters. </param>
        ///<param name="byteCount">The number of bytes to decode. </param>
        ///<param name="byteIndex">The index of the first byte to decode. </param>
        ///<param name="charIndex">The index at which to start writing the resulting set of characters. </param>
        ///<param name="bytes">The byte array containing the sequence of bytes to decode. </param>
        ///<exception cref="T:System.ArgumentOutOfRangeException">byteIndex or byteCount or charIndex is less than zero.-or- byteindex and byteCount do not denote a valid range in bytes.-or- charIndex is not a valid index in chars. </exception>
        ///<exception cref="T:System.ArgumentNullException">bytes is null.-or- chars is null. </exception>
        ///<exception cref="T:System.ArgumentException">chars does not have enough capacity from charIndex to the end of the array to accommodate the resulting characters. </exception><filterpriority>2</filterpriority>
        public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
        {
            if (chars == null)
                throw new ArgumentNullException("chars");
            if (bytes == null)
                throw new ArgumentNullException("bytes");
            if ((byteIndex < 0) || (byteIndex > bytes.Length) || (byteIndex + byteCount > bytes.Length))
                throw new ArgumentOutOfRangeException("byteIndex");
            if (charIndex < 0 || (charIndex >= chars.Length))
                throw new ArgumentOutOfRangeException("charIndex");

            int outSize = GetCharCount(bytes, byteIndex, byteCount);
            if (charIndex + outSize > chars.Length)
                throw new ArgumentException("chars");

            for (int i = 0; i < byteCount; ++i)
            {
                chars[charIndex + i] = (char) bytes[byteIndex + i];
            }

            return outSize;
        }

        internal static int GetMaxChars(int count)
        {
            return count;
        }
    }
}
